package com.yeskery.nut.script.function;

import com.yeskery.nut.script.parser.ScriptParseException;

import java.util.*;

/**
 * 默认的函数转换器
 * @author sprout
 * @version 1.0
 * 2022-05-15 13:31
 */
public class DefaultFunctionParser implements FunctionParser {

    /** 函数分隔符 */
    private static final char FUNCTION_SEPARATOR = '$';

    /** 函数开始符号 */
    private static final char FUNCTION_START_SYMBOL = '(';

    /** 函数结束符号 */
    private static final char FUNCTION_END_SYMBOL = ')';

    /** 函数上下文 */
    private final FunctionContext functionContext;

    /**
     * 构建默认的函数转换器
     * @param functionContext 函数上下文
     */
    public DefaultFunctionParser(FunctionContext functionContext) {
        this.functionContext = functionContext;
    }

    @Override
    public Collection<Stack<FunctionMetadataIndex>> parse(String expression) {
        return parse(expression, null);
    }

    @Override
    public Collection<Stack<FunctionMetadataIndex>> parse(String expression, FunctionContext extendFunctionContext) {
        Collection<Stack<FunctionMetadataIndex>> stackCollection = new LinkedHashSet<>();
        for (int i = 0; i < expression.length(); i++) {
            if (expression.charAt(i) == FUNCTION_SEPARATOR) {
                FunctionInfo functionInfo = getFunctionMetadataInfo(expression, i);
                if (functionInfo != null) {
                    i += functionInfo.getEndIndex();
                }
                Stack<FunctionMetadataIndex> stack = new Stack<>();
                if (functionInfo != null) {
                    appendFunctionMedataIndex(extendFunctionContext, stack, functionInfo, expression, null);
                }
                if (!stack.empty()) {
                    stackCollection.add(stack);
                }
            }
        }
        return stackCollection;
    }

    /**
     * 追加函数元数据索引对象
     * @param extendFunctionContext 额外扩展的函数空间
     * @param stack 函数元数据索引栈
     * @param functionInfo 函数信息
     * @param expression 表达式
     */
    private void appendFunctionMedataIndex(FunctionContext extendFunctionContext, Stack<FunctionMetadataIndex> stack,
                                           FunctionInfo functionInfo, String expression, FunctionMetadata parentFunctionMetadata) {
        Function function = functionContext.getFunction(functionInfo.getName());
        if (function == null) {
            if (extendFunctionContext == null) {
                throw new ScriptParseException("Unsupported Script Expression Function: ["
                        + functionInfo.getName() + "] At Expression: [" + expression + "]");
            } else {
                function = extendFunctionContext.getFunction(functionInfo.getName());
                if (function == null) {
                    throw new ScriptParseException("Unsupported Script Expression Function: ["
                            + functionInfo.getName() + "] At Expression: [" + expression + "]");
                }
            }
        }
        DefaultFunctionMetadata functionMetadata = new DefaultFunctionMetadata(function);
        stack.push(new FunctionMetadataIndex(functionMetadata, functionInfo.getStartIndex(),
                functionInfo.getEndIndex(), functionInfo.getBody(), parentFunctionMetadata));
        if (functionInfo.getChildFunctionInfos() != null) {
            for (FunctionInfo childFunctionInfo : functionInfo.getChildFunctionInfos()) {
                appendFunctionMedataIndex(extendFunctionContext, stack, childFunctionInfo, expression, functionMetadata);
            }
        }
    }

    /**
     * 获取函数元数据信息
     * @param expression 表达式
     * @param startIndex 函数开始索引
     * @return 函数元数据信息
     */
    private FunctionInfo getFunctionMetadataInfo(String expression, int startIndex) {
        if (startIndex >= expression.length() - 1) {
            return null;
        }
        FunctionInfo functionInfo = new FunctionInfo();
        List<FunctionInfo> childFunctionInfos = new ArrayList<>();
        functionInfo.setChildFunctionInfos(childFunctionInfos);
        for (int i = startIndex + 1; i < expression.length(); i++) {
            if (expression.charAt(i) == FUNCTION_SEPARATOR) {
                FunctionInfo childFunctionInfo = getFunctionMetadataInfo(expression, i);
                childFunctionInfos.add(childFunctionInfo);
                if (childFunctionInfo != null) {
                    i = childFunctionInfo.getEndIndex();
                }
            } else if (expression.charAt(i) == FUNCTION_START_SYMBOL) {
                String functionName = expression.substring(startIndex + 1, i);
                functionInfo.setName(functionName);
                functionInfo.setStartIndex(i);
            } else if (expression.charAt(i) == FUNCTION_END_SYMBOL) {
                functionInfo.setEndIndex(i);
                break;
            }
        }
        functionInfo = functionInfo.getValidFunction();
        if (functionInfo != null) {
            if (functionInfo.getStartIndex() + 1 == functionInfo.getEndIndex()) {
                functionInfo.setBody("");
            } else {
                functionInfo.setBody(expression.substring(functionInfo.getStartIndex() + 1, functionInfo.getEndIndex()));
            }
        }
        return functionInfo;
    }
}
